package com.tjetc.utils;

import cn.hutool.core.util.IdUtil;
import org.jetbrains.annotations.NotNull;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.stereotype.Component;

import java.util.Arrays;
import java.util.Timer;
import java.util.TimerTask;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;

/**
 * 我们自研的redis分布式锁，实现类Lock接口  满足JUC里AQS对锁的所有规范
 */
//@Component 我这个类不是通过容器获得，而是通过工厂直接 new()出来
public class RedisDistributeLock implements Lock {


    private StringRedisTemplate redisTemplate;

    private String lockName; //KEYS[1]
    private String uuidValue;//ARGV[1]
    private Long expireTime; //ARGV[2]

 /*   public RedisDistributeLock(StringRedisTemplate redisTemplate, String lockName) {
        this.redisTemplate = redisTemplate;
        this.lockName = lockName;
        this.uuidValue = IdUtil.simpleUUID()+":"+Thread.currentThread().getId();
        this.expireTime = 50L;
    }*/

    public RedisDistributeLock(StringRedisTemplate stringRedisTemplate, String lockName, String uuid) {
        this.redisTemplate = stringRedisTemplate;
        this.lockName = lockName;
        this.uuidValue = uuid + ":" + Thread.currentThread().getId();
        this.expireTime = 30L;
    }


    @Override
    public void lock() {
        tryLock();

    }

    @Override
    public boolean tryLock() {
        try {
            tryLock(-1L, TimeUnit.SECONDS);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        return false;
    }

    @Override
    public boolean tryLock(long time, @NotNull TimeUnit unit) throws InterruptedException {
        if (time == -1L) {
            String script = "if redis.call('exists', KEYS[1]) == 0   or   redis.call('hexists', KEYS[1]  ,ARGV[1] )==1   then redis.call('hincrby',KEYS[1], ARGV[1] ,1)  redis.call('expire', KEYS[1] , ARGV[2]) return 1   else  return 0  end";

           // System.out.println("lock():lockName:" + lockName + "\t" + "uuidValue:" + uuidValue);

            while (!redisTemplate.execute(new DefaultRedisScript<>(script, Boolean.class), Arrays.asList(lockName), uuidValue, String.valueOf(expireTime))) {
                TimeUnit.MILLISECONDS.sleep(60);
            }

            /*新建一个后台扫描程序
             * 来监测 key 目前的ttl ,是否到我们规定的1/2或者 1/3 来实现续期
             * */
            renewExpire();
            return true;
        }
        return false;
    }


    @Override
    public void unlock() {
      //  System.out.println("unlock():lockName:" + lockName + "\t" + "uuidValue:" + uuidValue);
        String script = "if redis.call('hexists',KEYS[1],ARGV[1]) == 0 then   " +
                "  return nil elseif  redis.call('hincrby',KEYS[1],ARGV[1],-1) == 0 then" +
                "  return redis.call('del',KEYS[1])  else   return 0 end";

        // nil = false( false用Long表示 相当于0)   1 =true   0 = false            用不到  expireTime 留着也无所谓
        Long flag = redisTemplate.execute(new DefaultRedisScript<>(script, Long.class), Arrays.asList(lockName), uuidValue, String.valueOf(expireTime));


        if (null == flag) {
            throw new RuntimeException("这把锁不存在");
        }

    }


    private void renewExpire() {
        String script =
                "if redis.call('hexists',KEYS[1],ARGV[1]) == 1 then" +
                " return redis.call('expire',KEYS[1],ARGV[2])" +
                "else  return 0 end ";
        new Timer().schedule(new TimerTask() {
            @Override
            public void run() {
              if(  redisTemplate.execute(new DefaultRedisScript<>(script,Boolean.class), Arrays.asList(lockName), uuidValue,String.valueOf(expireTime))){
                    renewExpire();
              }
            }
        }, (this.expireTime * 1000)/3);
    }




    /*下面两个暂时用不到 不再重写 不会考虑锁的中断 和线程之间的协调机制 ，因为在我们的业务里，分布式锁用完就删了
     * */

    @Override
    public void lockInterruptibly() throws InterruptedException {

    }

    @NotNull
    @Override
    public Condition newCondition() {
        return null;
    }
}
